package addcslashes

import (
	"bytes"
	"errors"
	"fmt"
)

const pointAscii = 46

// Quote string with slashes in a C style
// https://www.php.net/manual/en/function.addcslashes.php
func Do(str, charList string) string {
	if len(str) == 0 {
		return ""
	}
	if len(charList) == 0 {
		return str
	}

	buf := bytes.NewBuffer([]byte{})
	bs := []byte(str)
	cm, _ := CharMask([]byte(charList))
	mb := make(map[byte]struct{}, len(cm))
	for _, b := range cm {
		mb[b] = struct{}{}
	}
	l := len(bs)
	sm := map[string]string{
		"\n": "n",
		"\t": "t",
		"\r": "r",
		"\a": "a",
		"\b": "b",
		"\f": "f",
	}
	for i := 0; i < l; i++ {
		c := bs[i]
		if _, ok := mb[c]; !ok {
			buf.WriteByte(c)
			continue
		}
		if c < 32 || c > 126 {
			buf.WriteString("\\")
			s := string(c)
			if v, ok := sm[s]; ok {
				buf.WriteString(v)
			} else {
				buf.WriteString(fmt.Sprintf("%o", c))
			}
			continue
		}
		buf.WriteString("\\")
		buf.WriteByte(c)
	}
	return buf.String()
}

// CharMask Fills a 256-byte byte mask
// You can specify a range like 'a..f'
// it needs to be incrementing
func CharMask(charExp []byte) ([]byte, error) {
	l := len(charExp)
	buf := bytes.NewBuffer([]byte{})
	index := 0
	var err error
	for end := index + l; index < end; index++ {
		c := charExp[index]
		// ASCII . => 46
		if (index+3 < end) && charExp[index+1] == pointAscii && charExp[index+2] == pointAscii && charExp[index+3] >= c {
			input3 := int(charExp[index+3])
			input0 := int(c)
			il := input3 - input0 + 1
			for i := 0; i < il; i++ {
				buf.WriteByte(byte(input0 + i))
			}
			index += 3
		} else if (index+1 < end) && charExp[index] == pointAscii && charExp[index+1] == pointAscii {
			err = errors.New("invalid '..'-range")
			buf.WriteByte(c)
			continue
		} else {
			buf.WriteByte(c)
		}
	}
	return buf.Bytes(), err
}
